package com.kyub.spring.gears.web;

import com.kyub.spring.gears.model.*;
import com.kyub.spring.gears.services.ControllerDelegateService;
import com.kyub.spring.gears.services.DaoService;
import com.kyub.spring.gears.util.IntrospectionUtils;
import com.kyub.spring.gears.web.upload.FileUploadBean;
import com.kyub.spring.gears.PrjLogger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * Main  controller for crud operations
 */
@Controller("crudController")
@SessionAttributes({"commandPojo", "commandUpload"})
public class CrudController extends ControllerAncestor{


    @Autowired
    private PojoRegistry pojoRegistry;
    @Autowired
    private CrudControllerConfig crudConfig;

    private static final String BACK_POJO = "backPojo";

    @RequestMapping("/{pojo}/list")
    public ModelAndView list(ModelMap model, @PathVariable("pojo") String pojo, WebRequest request) {
        ModelAndView v = prepareListModel(pojo, request, "/list", true, "classic");
        return v;
    }

   


    @RequestMapping("/{pojo}/view/{"+ControllerAncestor.PARAM_ID_NAME+"}")
    public ModelAndView view(@PathVariable(PARAM_ID_NAME) Serializable id, ModelMap model, @PathVariable("pojo") String pojo) {
        ModelAndView v = buildModelAndView();

        v.addObject("pojoDescriptor", loadPojoDescriptor(pojo));

        v.addObject("pojo", pojoRegistry.getManager(pojo).load(id));
        v.setViewName("/" + crudConfig.templateFolder + "/view");
        return v;
    }
   

    @RequestMapping("/{pojo}/delete/{id}")
    public ModelAndView delete(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo) {
        final DaoService service = pojoRegistry.getManager(pojo);
        PrjLogger.debug(CrudController.class,"Deleting " + id);
        service.delete(service.load(id));
        ModelAndView v = new ModelAndView();
        v.setViewName("redirect:/" + crudConfig.resfullPrefix + "/" + pojo + "/list");
        return v;
    }

    /*
    http://localhost:8080/petClinic/clinic/pet/view/0
    http://localhost:8080/petClinic/clinic/pet/insert
    */
    @RequestMapping(value = "/{pojo}/insert", method = RequestMethod.GET)
    public ModelAndView setupEmptyForm(ModelMap model, WebRequest request, @PathVariable("pojo") String pojo) {
        ModelAndView v = buildModelAndView();
        v.addObject("commandUpload", new FileUploadBean());
        v.setViewName("/" + crudConfig.templateFolder + "/form");
        try {
            final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
            v.addObject("pojoDescriptor", descriptor);
            Object pojoInstance = descriptor.getPojoClass().newInstance();

             PrjLogger.debug(CrudController.class,"pojoInstance " + pojoInstance.getClass().getName());
            v.addObject(COMMAND_POJO_NAME, pojoInstance);

            v.addObject("relatedValues", this.getFactory().loadRealtedValues(descriptor, pojoRegistry));

        } catch (InstantiationException e) {
             PrjLogger.debug(CrudController.class,e.toString());
            if (PrjLogger.isDebugEnabled())
                e.printStackTrace();
        } catch (IllegalAccessException e) {
             PrjLogger.debug(CrudController.class,e.toString());
            if (PrjLogger.isDebugEnabled())
                e.printStackTrace();
        }
        return v;
    }

    @RequestMapping(value = "/{pojo}/update/{id}", method = RequestMethod.GET)
    public ModelAndView setupPopulatedForm(ModelMap model, WebRequest request, @PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo) {

         PrjLogger.debug(CrudController.class,"BackPojo: " + request.getParameter(BACK_POJO));
        ModelAndView v = buildModelAndView();
        v.addObject("commandUpload", new FileUploadBean());
        v.addObject("updateObject", true);
       v.setViewName("/" + crudConfig.templateFolder + "/form");

        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        v.addObject(COMMAND_POJO_NAME, pojoRegistry.getManager(pojo).load(id));
        v.addObject("relatedValues", this.getFactory().loadRealtedValues(descriptor, pojoRegistry));
        v.addObject("pojoDescriptor", descriptor);

        return v;
    }

    @RequestMapping(value = "/{pojo}/submit", method = RequestMethod.POST)
    public ModelAndView submit(@ModelAttribute(COMMAND_POJO_NAME) Object pojoInstance, BindingResult bindingResult, @PathVariable("pojo") String pojo, WebRequest request) {
         PrjLogger.debug(CrudController.class,"Submitted " + pojo + " ================ " + pojoInstance);
        final String backPojoName = request.getParameter(BACK_POJO);
         PrjLogger.debug(CrudController.class,"BackPojo: " + backPojoName);


        PojoRegistryEntry p = pojoRegistry.getPojoConf(pojo);


        final DaoService service = p.getDao();
         PrjLogger.debug(CrudController.class,"service instanceof ControllerDelegateService " + (service instanceof ControllerDelegateService));

        if (p.getValidator() != null) {

            p.getValidator().validate(pojoInstance, bindingResult);
        } else if (service instanceof ControllerDelegateService) {

            ((ControllerDelegateService) service).validate(pojoInstance, bindingResult);

        }


        ModelAndView v = new ModelAndView();
        if (bindingResult.hasErrors()) {
            v = buildModelAndView();
            v.setViewName("/" + crudConfig.templateFolder + "/form");

            final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
            v.addObject("pojoDescriptor", descriptor);
            v.addObject(COMMAND_POJO_NAME, pojoInstance);
            v.addObject("relatedValues", this.getFactory().loadRealtedValues(descriptor, pojoRegistry));

        } else {
            PrjLogger.debug(this,pojoInstance.toString());
            service.save(pojoInstance);
            String destinationPojo = pojo;
            if (backPojoName != null)
                destinationPojo = backPojoName;
            if(request.getParameter("is_ajax") != null){
                  v.setViewName("/" + crudConfig.templateFolder + "/empty");
            }
            else{
                v.setViewName("redirect:/" + crudConfig.resfullPrefix + "/" + destinationPojo + "/list");
            }
        }
        return v;
    }

    @RequestMapping("/{pojo}/download/{id}/{property}")
    public void download(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo, @PathVariable("property") String property, WebRequest request, HttpServletResponse response) {
        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        final StructPropertyDescriptor propertyDescriptor = (StructPropertyDescriptor) descriptor.getFormFiled(property);
        final FileLayout fileLayout = (FileLayout) propertyDescriptor.getLayout();
        final Object pojoInstance = pojoRegistry.getManager(pojo).load(id);


        try {

            final String path = (String) IntrospectionUtils.readPropertyValue(pojoInstance, propertyDescriptor.getName());
            if(path == null || path.length() == 0)
                throw new RuntimeException(" No file bound to " + propertyDescriptor.getName()+ " of "+descriptor.getPojoClass().getCanonicalName() +" " + id);
            final String fileName = path.substring(path.lastIndexOf("/") + 1);

             PrjLogger.debug(CrudController.class,"Path " + path);

             PrjLogger.debug(CrudController.class,"File name " + fileName);

            response.setContentType(fileLayout.getMimeType() + "; name=\"" + fileName + "\"");
            response.addHeader("content-disposition", "attachment; filename=\"" + fileName + "\"");

            response.getOutputStream().write(this.crudConfig.getFileStorage().load(path));
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    @RequestMapping("/{pojo}/executeupload/{id}/{property}")
    public ModelAndView executeUpload(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo, @PathVariable("property") String property, MultipartHttpServletRequest request, @ModelAttribute("commandUpload") Object uploadBean) {
        ModelAndView v = buildModelAndView();
        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        v.addObject("pojoDescriptor", descriptor);
        v.addObject("propertyName", property);
        //  final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request.;
        final MultipartFile multipartFile = request.getFile("file");

        final String originalFileName = multipartFile.getOriginalFilename();
        final StructPropertyDescriptor propertyDescriptor = (StructPropertyDescriptor) descriptor.getFormFiled(property);
        final FileLayout fileLayout = (FileLayout) propertyDescriptor.getLayout();
        final String path = fileLayout.getPersistPath() + "/" + pojo + "_" + id + "_" + property + "_" + System.currentTimeMillis() + "." + fileLayout.getExtension();
        FileUploadBean bean = (FileUploadBean) uploadBean;
        byte[] file = bean.getFile();
        if (file == null) {
            PrjLogger.error(CrudController.class,"File size null!");
        } else {
            try {
                this.crudConfig.getFileStorage().save(pojo, id, property, fileLayout, originalFileName, file);
                v.addObject("path", path);
            } catch (IOException e) {
                if (PrjLogger.isDebugEnabled()) {
                    e.printStackTrace();
                }
                v.addObject("error", e.toString());
            }
        }


        v.setViewName("/" + crudConfig.templateFolder + "/upload");
        return v;
    }


    @RequestMapping("/{pojo}/viewrelated/{id}/{property}")
    public ModelAndView viewRelated(@PathVariable(PARAM_ID_NAME) long id, ModelMap model, @PathVariable("pojo") String pojo, @PathVariable("property") String property) {
        ModelAndView v = buildModelAndView();

        final PojoDescriptor pojoOwner = loadPojoDescriptor(pojo);
        v.addObject("pojoOwner", pojoOwner);
         PrjLogger.debug(CrudController.class,"pojoOwner " + pojoOwner);


        final CollectionPropertyDescriptor propertyDescriptor = (CollectionPropertyDescriptor) pojoOwner.getProperty(property);
         PrjLogger.debug(CrudController.class,"pojoDescriptor for property " + property + " " + propertyDescriptor.getPojoDescr());
        v.addObject("pojoDescriptor", propertyDescriptor.getPojoDescr());


        try {
            v.addObject("pojoList", IntrospectionUtils.readPropertyValue(pojoRegistry.getManager(pojo).load(id), property));
        } catch (Exception e) {
             PrjLogger.debug(CrudController.class,e.toString());
            // if (PrjLogger.isDebugEnabled())
            e.printStackTrace();
        }
        v.setViewName("/" + crudConfig.templateFolder + "/viewrelated");
        return v;
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {

           super.initBinderDelegate(binder);
    }

    public PojoRegistry getPojoRegistry() {
        return pojoRegistry;
    }

    public void setPojoRegistry(PojoRegistry pojoRegistry) {
        this.pojoRegistry = pojoRegistry;

    }

    public CrudControllerConfig getCrudConfig() {
        return crudConfig;
    }

    public void setCrudConfig(CrudControllerConfig crudConfig) {
        this.crudConfig = crudConfig;
    }
}
